﻿using Dotnet.Utils.Utility.ObjectExtensions.To;
using NodaTime;
using System;
using System.Text;

namespace Dotnet.Utils.Utility.ObjectExtensions.Date
{
    /// <summary>
    /// DateTime扩展
    /// </summary>
    public static class DateTimeExtension
    {
        #region 获取系统当前时间的几个方法（返回时间+格式化后的时间字符串）

        /// <summary>
        /// 获取系统当前时间
        /// </summary>
        /// <returns>系统当前时间</returns>
        public static DateTime GetSysDateTimeNow()
        {
            Instant now = SystemClock.Instance.GetCurrentInstant();
            var shanghaiZone = DateTimeZoneProviders.Tzdb["Asia/Shanghai"];
            return now.InZone(shanghaiZone).ToDateTimeUnspecified();
        }

        /// <summary>
        /// 获取系统当前时间格式化字符串 24小时制 被格式化为 (yyyy-MM-dd HH:mm:ss.fff)
        /// </summary>
        /// <returns>系统当前格式化的时间字符串(yyyy-MM-dd HH:mm:ss.fff)</returns>
        public static string GetSysDateTimeNowStringYMD24HMSF()
        {
            return GetSysDateTimeNow().ToStringYMD24HMSF();
        }

        /// <summary>
        /// 获取系统当前时间格式化字符串 12小时制 被格式化为 (yyyy-MM-dd hh:mm:ss.fff)
        /// </summary>
        /// <returns>系统当前格式化的时间字符串(yyyy-MM-dd hh:mm:ss.fff)</returns>
        public static string GetSysDateTimeNowStringYMD12HMSF(this DateTime time)
        {
            return GetSysDateTimeNow().ToStringYMD12HMSF();
        }

        /// <summary>
        /// 获取系统当前时间格式化字符串 24小时制 被格式化为 (yyyy-MM-dd HH:mm:ss)
        /// </summary>
        /// <returns>系统当前格式化的时间字符串(yyyy-MM-dd HH:mm:ss)</returns>
        public static string GetSysDateTimeNowStringYMD24HMS(this DateTime time)
        {
            return GetSysDateTimeNow().ToStringYMD24HMS();
        }

        /// <summary>
        /// 获取系统当前时间格式化字符串 12小时制 被格式化为 (yyyy-MM-dd hh:mm:ss)
        /// </summary>
        /// <returns>系统当前格式化的时间字符串(yyyy-MM-dd hh:mm:ss)</returns>
        public static string GetSysDateTimeNowStringYMD12HMS(this DateTime time)
        {
            return GetSysDateTimeNow().ToStringYMD12HMS();
        }

        /// <summary>
        /// 获取系统当前时间格式化字符串  被格式化为 (yyyy-MM-dd)
        /// </summary>
        /// <returns>系统当前格式化的时间字符串(yyyy-MM-dd)</returns>
        public static string GetSysDateTimeNowStringYMD(this DateTime time)
        {
            return GetSysDateTimeNow().ToStringYMD();
        }

        #endregion 获取系统当前时间的几个方法（返回时间+格式化后的时间字符串）

        #region DateTime 扩展几个 格式方法

        /// <summary>
        /// 时间 格式化 24小时制 被格式化为  (yyyy-MM-dd HH:mm:ss.fff)
        /// </summary>
        /// <param name="time">被格式的时间</param>
        /// <returns>格式化后的时间字符串(yyyy-MM-dd HH:mm:ss.fff)</returns>
        public static string ToStringYMD24HMSF(this DateTime time)
        {
            return time.ToString("yyyy-MM-dd HH:mm:ss.fff");
        }

        /// <summary>
        /// 时间 格式化 12小时制 被格式化为  (yyyy-MM-dd hh:mm:ss.fff)
        /// </summary>
        /// <param name="time">被格式化时间</param>
        /// <returns>格式化后的时间字符串(yyyy-MM-dd hh:mm:ss.fff)</returns>
        public static string ToStringYMD12HMSF(this DateTime time)
        {
            return time.ToString("yyyy-MM-dd hh:mm:ss.fff");
        }

        /// <summary>
        /// 时间 格式化 24小时制 被格式化为  (yyyy-MM-dd HH:mm:ss)
        /// </summary>
        /// <param name="time">被格式化时间</param>
        /// <returns>格式化后的时间字符串(yyyy-MM-dd HH:mm:ss)</returns>
        public static string ToStringYMD24HMS(this DateTime time)
        {
            return time.ToString("yyyy-MM-dd HH:mm:ss");
        }

        /// <summary>
        /// 时间 格式化 12小时制 被格式化为  (yyyy-MM-dd hh:mm:ss)
        /// </summary>
        /// <param name="time">被格式化时间</param>
        /// <returns>格式化后的时间字符串(yyyy-MM-dd hh:mm:ss)</returns>
        public static string ToStringYMD12HMS(this DateTime time)
        {
            return time.ToString("yyyy-MM-dd hh:mm:ss");
        }

        /// <summary>
        /// 时间 格式化  被格式化为  (yyyy-MM-dd)
        /// </summary>
        /// <param name="time">被格式化时间</param>
        /// <returns>格式化后的时间字符串(yyyy-MM-dd)</returns>
        public static string ToStringYMD(this DateTime time)
        {
            return time.ToString("yyyy-MM-dd");
        }

        #endregion DateTime 扩展几个 格式方法

        #region 获取时间戳

        /// <summary>
        /// 获取时间戳(秒)
        /// </summary>
        /// <returns>秒时间戳</returns>
        public static long GetSecondTimestamp()
        {
            // 以1970-1-1 为时间开始 同系统当前时间的秒差值即为秒时间戳
            TimeSpan ts = GetSysDateTimeNow() - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds);
        }

        /// <summary>
        /// 获取时间戳（毫秒）
        /// </summary>
        /// <returns>毫秒时间戳</returns>
        public static long GetMilliSecondTimestamp()
        {
            // 以1970-1-1 为时间开始 同系统当前时间的毫秒差值即为毫秒时间戳
            TimeSpan ts = GetSysDateTimeNow() - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalMilliseconds);
        }

        #endregion 获取时间戳

        #region 将一个时间戳转换为一个时间

        /// <summary>
        /// 将一个秒时间戳转换为时间格式(秒)
        /// </summary>
        /// <param name="secondTimestamp">秒时间戳</param>
        /// <returns>转换后的时间</returns>
        public static DateTime? SecondStampToDateTime(long secondTimestamp)
        {
            //  做一个简单的判断
            if (secondTimestamp <= 0)
            {
                return null;
            }

            // 以1970-1-1 为时间开始，通过计算与之的时间差，来计算其对应的时间
            DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            dateTime = dateTime.AddSeconds(secondTimestamp).ToLocalTime();
            return dateTime;
        }

        /// <summary>
        /// 将一个字符串秒时间戳转换为时间格式(秒)
        /// </summary>
        /// <param name="secondTimestampStr">字符串秒时间戳</param>
        /// <returns>转换后的时间</returns>
        public static DateTime? SecondStampToDateTime(string secondTimestampStr)
        {
            // 如果为空，那么直接返回null
            if (string.IsNullOrEmpty(secondTimestampStr))
            {
                return null;
            }

            // 首先将字符串时间戳转换为数字
            long secondTimestamp = 0;
            long.TryParse(secondTimestampStr, out secondTimestamp);

            // 调用
            return SecondStampToDateTime(secondTimestamp);
        }

        /// <summary>
        /// 将一个字符串毫秒时间戳转换为时间格式(毫秒)
        /// </summary>
        /// <param name="secondTimestampStr">字符串毫秒时间戳</param>
        /// <returns>转换后的时间</returns>
        public static DateTime? MilliSecondStampToDateTime(long secondTimestamp)
        {
            //  做一个简单的判断
            if (secondTimestamp <= 0)
            {
                return null;
            }

            // 以1970-1-1 为时间开始，通过计算与之的时间差，来计算其对应的时间
            DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            dateTime = dateTime.AddMilliseconds(secondTimestamp).ToLocalTime();

            return dateTime;
        }

        /// <summary>
        /// 将一个毫秒时间戳转换为时间格式(毫秒)
        /// </summary>
        /// <param name="milliSecondStampStr">毫秒时间戳</param>
        /// <returns>转换后的时间</returns>
        public static DateTime? MilliSecondStampToDateTime(string milliSecondStampStr)
        {
            // 如果为空，那么直接返回null
            if (string.IsNullOrEmpty(milliSecondStampStr))
            {
                return null;
            }

            // 首先将字符串时间戳转换为数字
            long milliSecondStamp = 0;
            long.TryParse(milliSecondStampStr, out milliSecondStamp);

            // 调用
            return MilliSecondStampToDateTime(milliSecondStamp);
        }

        #endregion 将一个时间戳转换为一个时间

        /// <summary>
        /// 获取某天开始时间
        /// </summary>
        /// <param name="dateTime">某天中的任意时间</param>
        /// <returns></returns>
        public static DateTime DayStart(this DateTime dateTime) => dateTime.Date;

        /// <summary>
        /// 获取某天结束时间
        /// </summary>
        /// <param name="dateTime">某天中的任意时间</param>
        /// <returns></returns>
        public static DateTime DayEnd(this DateTime dateTime) => dateTime.DayStart().AddDays(1).AddMilliseconds(-1);

        /// <summary>
        /// 获取某天的始末时间
        /// </summary>
        /// <param name="dateTime">某天中的任意时间</param>
        /// <returns>(Start, End)</returns>
        public static ValueTuple<DateTime, DateTime> DayStartEnd(this DateTime dateTime) => new ValueTuple<DateTime, DateTime>(dateTime.DayStart(), dateTime.DayEnd());

        /// <summary>
        /// 获取某天的所属周的开始时间
        /// </summary>
        /// <param name="dateTime">某周中的任意天日期</param>
        /// <param name="firstDay">一周的第一天[周日还是周一或者其他]</param>
        /// <returns></returns>
        public static DateTime WeekStart(this DateTime dateTime, DayOfWeek firstDay) => dateTime.AddDays(-dateTime.DayOfWeek.DayNumber()).DayStart().AddDays((int)firstDay);

        /// <summary>
        /// 获取某天的所属周的结束时间
        /// </summary>
        /// <param name="dateTime">某周中的任意天日期</param>
        /// <param name="firstDay">一周的第一天[周日还是周一或者其他]</param>
        /// <returns></returns>
        public static DateTime WeekEnd(this DateTime dateTime, DayOfWeek firstDay) => dateTime.WeekStart(firstDay).AddDays(6).DayEnd();

        /// <summary>
        /// 获取某天所属周的开始和结束时间
        /// </summary>
        /// <param name="dateTime">某周中的任意天日期</param>
        /// <param name="firstDay">一周的第一天[周日还是周一或者其他]</param>
        /// <returns></returns>
        public static ValueTuple<DateTime, DateTime> WeekStartEnd(this DateTime dateTime, DayOfWeek firstDay) => new ValueTuple<DateTime, DateTime>(dateTime.WeekStart(firstDay), dateTime.WeekEnd(firstDay));

        /// <summary>
        /// 获取某月的开始时间
        /// </summary>
        /// <param name="dateTime">某月中的任意天日期</param>
        /// <returns></returns>
        public static DateTime MonthStart(this DateTime dateTime) => dateTime.DayStart().AddDays(1 - dateTime.Day);

        /// <summary>
        /// 获取某月的结束时间
        /// </summary>
        /// <param name="dateTime">某月中的任意天日期</param>
        /// <returns></returns>
        public static DateTime MonthEnd(this DateTime dateTime) => dateTime.MonthStart().AddMonths(1).AddMilliseconds(-1);

        /// <summary>
        /// 获取某月的始末时间
        /// </summary>
        /// <param name="dateTime">某天中的任意时间</param>
        /// <returns>(Start, End)</returns>
        public static ValueTuple<DateTime, DateTime> MonthStartEnd(this DateTime dateTime) => new ValueTuple<DateTime, DateTime>(dateTime.MonthStart(), dateTime.MonthEnd());

        /// <summary>
        /// 获取某年的开始时间
        /// </summary>
        /// <param name="dateTime">某年中的任意一天</param>
        /// <returns></returns>
        public static DateTime YearStart(this DateTime dateTime) => dateTime.Date.AddMonths(1 - dateTime.Month).AddDays(1 - dateTime.Day).DayStart();

        /// <summary>
        /// 获取某年的结束时间
        /// </summary>
        /// <param name="dateTime">某年中的任意一天</param>
        /// <returns></returns>
        public static DateTime YearEnd(this DateTime dateTime) => dateTime.YearStart().AddYears(1).AddMilliseconds(-1);

        /// <summary>
        /// 获取某年的始末时间
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns>(Start, End)</returns>
        public static ValueTuple<DateTime, DateTime> YearStartEnd(this DateTime dateTime) => new ValueTuple<DateTime, DateTime>(dateTime.YearStart(), dateTime.YearEnd());

        /// <summary>
        /// 根据周数和年份获取某周的开始和结束时间
        /// </summary>
        /// <param name="week">一年中自然周数</param>
        /// <param name="year">年份</param>
        /// <param name="firstDay">一周开始时间(周一或者周日)</param>
        /// <returns></returns>
        public static ValueTuple<DateTime, DateTime> WeekStartEndByNumber(this int week, int year, DayOfWeek firstDay) => new DateTime(year, 1, 1).AddDays((week - 1) * 7).WeekStartEnd(firstDay);

        /// <summary>
        /// 根据月份获取某月的开始时间和结束时间
        /// </summary>
        /// <param name="month">月份</param>
        /// <param name="year">年份</param>
        /// <returns></returns>
        public static ValueTuple<DateTime, DateTime> MonthStartEndByMonth(this int month, int year) => month < 1 | month > 13 ? throw new Exception("非法月份") : new DateTime(year, month, 2).MonthStartEnd();

        /// <summary>
        /// 获取整周的星期数字形式
        /// </summary>
        /// <param name="day"></param>
        /// <returns></returns>
        public static int DayNumber(this DayOfWeek day) =>
            day switch
            {
                DayOfWeek.Friday => 5,
                DayOfWeek.Monday => 1,
                DayOfWeek.Saturday => 6,
                DayOfWeek.Thursday => 4,
                DayOfWeek.Tuesday => 2,
                DayOfWeek.Wednesday => 3,
                _ => 7
            };

        /// <summary>
        /// 将0-7的数字转化成DayOfWeek类型
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        public static DayOfWeek ToDayOfWeek(this int number) =>
            number > 7 | number < 0
                ? throw new Exception("please input 0-7")
                : number switch
                {
                    0 => DayOfWeek.Sunday,
                    1 => DayOfWeek.Monday,
                    2 => DayOfWeek.Tuesday,
                    3 => DayOfWeek.Wednesday,
                    4 => DayOfWeek.Thursday,
                    5 => DayOfWeek.Friday,
                    6 => DayOfWeek.Saturday,
                    _ => DayOfWeek.Sunday
                };

        /// <summary>
        /// 获取一周星期对应中文名
        /// </summary>
        /// <param name="day"></param>
        /// <param name="type"> 1 ? "周" : "星期"</param>
        /// <returns>周(一至日(天))||星期(一至日(天))</returns>
        public static string DayName(this int day, int type = 1)
        {
            var name = day switch
            {
                1 => "一",
                2 => "二",
                3 => "三",
                4 => "四",
                5 => "五",
                6 => "六",
                0 => type == 1 ? "日" : "天",
                _ => "错误"
            };
            return $"{(type == 1 ? "周" : "星期")}{name}";
        }

        /// <summary>
        /// 获取一周星期对应中文名
        /// </summary>
        /// <param name="day"></param>
        /// <param name="type"> 1 ? "周" : "星期"</param>
        /// <returns>周(一至日(天))||星期(一至日(天))</returns>
        public static string DayName(this DayOfWeek day, int type = 1)
        {
            var name = day switch
            {
                DayOfWeek.Monday => "一",
                DayOfWeek.Tuesday => "二",
                DayOfWeek.Wednesday => "三",
                DayOfWeek.Thursday => "四",
                DayOfWeek.Friday => "五",
                DayOfWeek.Saturday => "六",
                DayOfWeek.Sunday => type == 1 ? "日" : "天",
                _ => "错误"
            };
            return $"{(type == 1 ? "周" : "星期")}{name}";
        }

        /// <summary>
        /// 获取某个日期从另一个日期开始的间隔周数,当要计算的日期小于起始日期时,返回-1
        /// </summary>
        /// <param name="point">起始日期</param>
        /// <param name="date">要计算的日期</param>
        /// <returns></returns>
        public static int WeekNoFromPoint(DateTime point, DateTime? date)
        {
            date ??= DateTime.Now;
            if (date < point) return -1;
            var dayDiff = point.DayOfWeek == DayOfWeek.Sunday ? 6 : (int)point.DayOfWeek - 1;
            var first_monday = point.AddDays(-dayDiff);
            var daysCount = (date.Value - first_monday).TotalDays;
            return (int)(daysCount / 7) + (daysCount % 7 == 0 ? 0 : 1);
        }

#if !NETSTANDARD

        /// <summary>
        /// 将DateTime转化成DateOnly
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static DateOnly ToDateOnly(this DateTime dateTime) => DateOnly.FromDateTime(dateTime);

        /// <summary>
        /// 将DateTime转化成TimeOnly
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static TimeOnly ToTimeOnly(this DateTime dateTime) => TimeOnly.FromDateTime(dateTime);

#endif

        public static DateTime GetTime(string timeStamp)
        {
            DateTime dateTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            long ticks = long.Parse(timeStamp + "0000000");
            TimeSpan value = new TimeSpan(ticks);
            return dateTime.Add(value);
        }

        public static DateTime ToBeiJingTime(DateTime utcDatetime)
        {
            DateTime result;
            if (utcDatetime.Kind != DateTimeKind.Utc)
            {
                result = utcDatetime;
            }
            else
            {
                TimeZoneInfo destinationTimeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
                result = TimeZoneInfo.ConvertTimeFromUtc(utcDatetime, destinationTimeZone);
            }
            return result;
        }

        public static long GetUnixTimestamp(DateTime time)
        {
            long ticks = (time - TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1), TimeZoneInfo.Local).Add(new TimeSpan(8, 0, 0))).Ticks;
            return ConvertUtil.ToLong(ticks / 10000000L);
        }

        public static DateTime GetTimeFromUnixTimestamp(long timestamp)
        {
            DateTime dateTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1), TimeZoneInfo.Local);
            TimeSpan value = new TimeSpan(long.Parse(timestamp + "0000000"));
            return dateTime.Add(value).Add(new TimeSpan(8, 0, 0));
        }

        public static long ToUnixTimestamp(this DateTime time)
        {
            return GetUnixTimestamp(time);
        }

        public static DateTime FirstDayOfTheMonth(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, 1);
        }

        public static DateTime LastDayOfTheMonth(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month));
        }

        public static DateTime LastDayOfWeek(this DateTime date, DayOfWeek dayOfweek)
        {
            int num = -7;
            DateTime result;
            do
            {
                result = date.AddDays(num);
                num++;
            }
            while (result.DayOfWeek != dayOfweek);
            return result;
        }

        public static DateTime NextDayOfWeek(this DateTime date, DayOfWeek dayOfweek)
        {
            int num = 7;
            DateTime result;
            do
            {
                result = date.AddDays(num);
                num--;
            }
            while (result.DayOfWeek != dayOfweek);
            return result;
        }

        public static string ToDateTimeString(this DateTime dateTime)
        {
            return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
        }

        public static string ToDateString(this DateTime dateTime)
        {
            return dateTime.ToString("yyyy-MM-dd");
        }

        public static string ToTimeString(this DateTime dateTime)
        {
            return dateTime.ToString("HH:mm:ss");
        }

        public static string ToChineseDateString(this DateTime dateTime)
        {
            return string.Format("{0}年{1}月{2}日", dateTime.Year, dateTime.Month, dateTime.Day);
        }

        public static string Description(this TimeSpan span)
        {
            StringBuilder stringBuilder = new StringBuilder();
            if (span.Days > 0)
            {
                stringBuilder.AppendFormat("{0}天", span.Days);
            }
            if (span.Hours > 0)
            {
                stringBuilder.AppendFormat("{0}小时", span.Hours);
            }
            if (span.Minutes > 0)
            {
                stringBuilder.AppendFormat("{0}分", span.Minutes);
            }
            if (span.Seconds > 0)
            {
                stringBuilder.AppendFormat("{0}秒", span.Seconds);
            }
            if (span.Milliseconds > 0)
            {
                stringBuilder.AppendFormat("{0}毫秒", span.Milliseconds);
            }
            string result;
            if (stringBuilder.Length > 0)
            {
                result = stringBuilder.ToString();
            }
            else
            {
                result = string.Format("{0}毫秒", span.TotalSeconds * 1000.0);
            }
            return result;
        }
    }
}